David's Blog

Using `zap` for Efficient Logging in Go

By David Li on 2024-05-09T12:01:52.000Z

Using zap for Efficient Logging in Go

zap is a high-performance logging library developed by Uber Technologies, Inc. for the Go programming language. It provides a flexible, easy-to-use, and blazingly fast logging experience. This article will explore the benefits of using zap in your Go projects and will walk you through the process of integrating it into your application.

Why Use zap?

There are several reasons to consider using zap for logging in your Go applications:

  1. Performance: zap is designed for high performance, with minimal allocations and fast encoding of log entries.
  2. Structured Logging: zap supports structured logging out of the box, which means that you can log data in a structured format (e.g., JSON) for easy consumption by log aggregation systems.
  3. Level-based Logging: zap provides level-based logging, allowing you to control the verbosity of your logs at runtime.
  4. Customizability: zap is highly customizable, allowing you to encode and output logs in different formats, set custom log levels, and create your loggers.

Installing zap

To get started with zap, you’ll need to install it in your Go project. You can do this using Go modules by adding the following import statement to your Go file:

import "go.uber.org/zap"

Then, run go mod tidy to download the library and update your go.mod file.

Creating a Basic Logger

To create a basic logger using zap, you can use the NewProduction or NewDevelopment functions. The NewProduction function creates a logger with production-suitable defaults, while NewDevelopment creates a logger with development-friendly defaults (e.g., more verbose output).

Here’s an example of creating a logger using NewDevelopment:

package main

import (
	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewDevelopment()
	if err != nil {
		panic(err)
	}
	defer logger.Sync() // Ensure that all logs are flushed before the application exits

	logger.Info("This is an informational log message")
	logger.Warn("This is a warning log message")
	logger.Error("This is an error log message")
}

Level-based Logging

zap supports level-based logging, allowing you to control the verbosity of your logs at runtime. By default, zap has five log levels: Debug, Info, Warn, Error, and DPanic. You can set the minimum log level for your logger by passing a configuration object when creating the logger.

Here’s an example of creating a logger that only logs messages with a level of Warn or higher:

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	config := zap.Config{
		Level:       zap.NewAtomicLevelAt(zap.WarnLevel),
		Development: false,
		Encoding:    "json",
		EncoderConfig: zapcore.EncoderConfig{
			MessageKey: "msg",
			LevelKey:   "level",
			TimeKey:    "ts",
			NameKey:    "logger",
			CallerKey:  "caller",
			StacktraceKey: "stacktrace",
			LineEnding: zapcore.DefaultLineEnding,
			EncodeLevel: zapcore.LowercaseLevelEncoder,
			EncodeTime: zapcore.ISO8601TimeEncoder,
			EncodeDuration: zapcore.SecondsDurationEncoder,
			EncodeCaller: zapcore.ShortCallerEncoder,
		},
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stderr"},
	}

	logger, err := config.Build()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("This log message will not be printed")
	logger.Warn("This is a warning log message")
	logger.Error("This is an error log message")
}

In this example, the Level field of the zap.Config struct is set to zap.WarnLevel, so only log messages with a level of Warn or higher will be printed.

Structured Logging

zap supports structured logging, allowing you to log data in a structured format for easy consumption by log aggregation systems. To log structured data, you can use the With method to add key-value pairs to your log entries.

Here’s an example of logging structured data using zap:

package main

import (
	"go.uber.org/zap"
)

func main() {
	logger, err := zap.NewDevelopment()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("Structured log message",
		zap.String("key1", "value1"),
		zap.Int("key2", 42),
		zap.Bool("key3", true),
	)
}

Customizing Log Output Format

By default, zap logs messages in a JSON format. However, you can customize the output format by providing a custom encoder configuration in the zap.Config struct.

Here’s an example of using a console encoder for log output:

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	config := zap.Config{
		Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
		Development: false,
		Encoding:    "console",
		EncoderConfig: zapcore.EncoderConfig{
			MessageKey: "msg",
			LevelKey:   "level",
			TimeKey:    "ts",
			NameKey:    "logger",
			CallerKey:  "caller",
			StacktraceKey: "stacktrace",
			LineEnding: zapcore.DefaultLineEnding,
			EncodeLevel: zapcore.CapitalLevelEncoder,
			EncodeTime: zapcore.ISO8601TimeEncoder,
			EncodeDuration: zapcore.SecondsDurationEncoder,
			EncodeCaller: zapcore.ShortCallerEncoder,
		},
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stderr"},
	}

	logger, err := config.Build()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("This is an informational log message with console encoding")
}

In this example, the Encoding field of the zap.Config struct is set to "console", which configures the logger to use the console encoder for log output. The console encoder produces human-readable output that is more suitable for development environments.

Creating Custom Loggers

If the built-in loggers provided by zap do not meet your requirements, you can create custom loggers by defining custom zapcore.Core instances and using the zap.New function.

Here’s an example of creating a custom logger that logs messages with a custom timestamp format:

package main

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"time"
)

func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05"))
}

func main() {
	config := zap.Config{
		Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
		Development: false,
		Encoding:    "console",
		EncoderConfig: zapcore.EncoderConfig{
			MessageKey: "msg",
			LevelKey:   "level",
			TimeKey:    "ts",
			NameKey:    "logger",
			CallerKey:  "caller",
			StacktraceKey: "stacktrace",
			LineEnding: zapcore.DefaultLineEnding,
			EncodeLevel: zapcore.CapitalLevelEncoder,
			EncodeTime: customTimeEncoder,
			EncodeDuration: zapcore.SecondsDurationEncoder,
			EncodeCaller: zapcore.ShortCallerEncoder,
		},
		OutputPaths:      []string{"stdout"},
		ErrorOutputPaths: []string{"stderr"},
	}

	logger, err := config.Build()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("This is an informational log message with a custom timestamp format")
}

In this example, we define a custom customTimeEncoder function that formats timestamps using a custom format, and pass it to the EncodeTime field of the EncoderConfig.

Conclusion

zap is a powerful and efficient logging library for Go that provides many useful features, such as structured logging, level-based logging, and log output customization. By using zap in your Go projects, you can improve your application’s logging capabilities and make it easier to monitor and debug your applications in both development and production environments.

© Copyright 2024 by FriendlyUsers Tech Blog. Built with ♥ by FriendlyUser. Last updated on 2024-05-06.